package com.jh.hts_jdk.utils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.apache.commons.math3.linear.RealMatrix;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import com.jh.hts_jdk.io.XlsxReader;
import com.jh.hts_jdk.item.Matrix;

import htsjdk.samtools.Cigar;
import htsjdk.samtools.CigarElement;
import htsjdk.samtools.CigarOperator;
import htsjdk.samtools.SAMRecord;
import htsjdk.samtools.fastq.FastqReader;
import htsjdk.samtools.fastq.FastqRecord;

public class Tools {

    public static List<String> expendDegenerateCodon(String degenerate_codon) {
        List<String> old_list = Arrays.asList(new String[] { "" });
        List<String> new_list = new ArrayList<>();
        for (int i = 0; i < degenerate_codon.length(); i++) {
            Character d_base = Character.valueOf(degenerate_codon.charAt(i));
            String expend_base_str = Constant.AMBIGUOUS_BASE_TABLE.get(d_base);
            for (int j = 0; j < expend_base_str.length(); j++) {
                String e_base = String.valueOf(expend_base_str.charAt(j));
                for (String old_str : old_list) {
                    String new_str = old_str + e_base;
                    new_list.add(new_str);
                }

            }
            old_list = new_list;
            new_list = new ArrayList<>();
        }
        return old_list;
    }

    /**
     * 获取base_list 中所有元素的组合，k代表组合长度
     * 
     * @param base_list
     * @return 若参数错误返回空列表
     */
    public static String[] getSequenceCombine(String[] base_list, int k) {
        LinkedList<String> old_list = new LinkedList<>();
        LinkedList<String> new_list = new LinkedList<>();
        old_list.add("");

        for (int i = 0; i < k; i++) {
            for (String old_str : old_list) {
                for (String item : base_list) {
                    String new_str = old_str + item;
                    new_list.add(new_str);
                }
            }
            old_list = new_list;
            new_list = new LinkedList<>();
        }
        return old_list.toArray(new String[0]);
    }

    public static String[] getSequenceCombine(Character[] base_list, int k) {
        String[] new_list = new String[base_list.length];
        for (int i = 0; i < new_list.length; i++) {
            new_list[i] = base_list[i].toString();
        }
        return getSequenceCombine(new_list, k);
    }

    /**
     * 获取简并密码子的所有组合
     * 
     * @param degenerate_codon 简并密码子(例如NNK)
     * @param ref_codon        参考密码子
     * @return
     */

    public static Matrix<String, String> getSequenceCombine(String degenerate_codon, String ref_codon) {

        degenerate_codon = degenerate_codon.toUpperCase();
        if (ref_codon != null) {
            ref_codon = ref_codon.toUpperCase();
            if (ref_codon.length() != degenerate_codon.length()) {
                throw new IllegalArgumentException("sequence length not consistent");
            }
        }

        // 检查简并密码子是否书写正确
        for (int i = 0; i < degenerate_codon.length(); i++) {
            char base = degenerate_codon.charAt(i);
            if (Constant.AMBIGUOUS_BASE_TABLE.containsKey(base)) {
                continue;
            } else if (base == '-' && ref_codon != null) {
                degenerate_codon = degenerate_codon.replaceFirst("\\-", String.valueOf(ref_codon.charAt(i)));
            } else {
                throw new IllegalArgumentException("unknow degenerate base: " + degenerate_codon);
            }
        }
        // 初始化碱基频率矩阵

        Matrix<String, String> res = new Matrix<>(new String[] { "Frequency" }, Constant.CODON_LIST, 0);
        for (String c : expendDegenerateCodon(degenerate_codon)) {
            res.setValue(0, c, 1);
        }

        double frequency_sum = res.getRowSum(0);
        for (int j = 0; j < res.getColName().length; j++) {
            res.setValue(0, j, res.getValue(0, j) / frequency_sum);
        }
        // res为1行n列，每一列表示对应密码子的频率
        return res;
    }

    public static <T> T randomChoices(T[] population, double[] weights) {
        return randomChoices(population, weights, 1).get(0);
    }

    public static <T> List<T> randomChoices(T[] population, double[] weights, int k) {
        return randomChoices(population, weights, k, true);
    }

    public static <T> List<T> randomChoices(T[] population, double[] weights, int k, boolean repeat) {
        if (weights == null) {
            weights = new double[population.length];
            Arrays.fill(weights, 1);
        } else if (weights.length != population.length) {
            throw new IllegalArgumentException("weights length not match population length when generate random number");
        }
        if (k <= 0) {
            k = 1;
        }
        if (!repeat && k > population.length) {
            throw new IllegalArgumentException("k large then population length when get not repeat values");
        }
        double max_value = Arrays.stream(weights).sum();
        List<T> selected_values = new ArrayList<T>(k);

        for (int l = 0; l < k; l++) {
            double random_value = Math.random() * max_value;
            double count = 0;
            for (int i = 0; i < population.length; i++) {
                count += weights[i];
                if (count >= random_value) {
                    selected_values.add(population[i]);
                    if (!repeat) {
                        // 确保选取的值不重复
                        max_value -= weights[i];
                        weights[i] = 0;
                    }
                    break;
                }
            }
        }
        return selected_values;
    }

    public static double[] getMatrixRowSum(RealMatrix matrix) {
        return Arrays.stream(matrix.getData()).mapToDouble(row -> Arrays.stream(row).sum()).toArray();
    }

    public static double[] getMatrixColumnSum(RealMatrix matrix) {
        return Arrays.stream(matrix.transpose().getData()).mapToDouble(row -> Arrays.stream(row).sum()).toArray();
    }

    public static double getMAtrixSum(RealMatrix matrix) {
        return Arrays.stream(getMatrixRowSum(matrix)).sum();
    }

    public static void test() {
        System.out.println(new File("./test/rrr/out.pdf").getAbsoluteFile());
    }

    public static void ThreadsWait(Thread[] T) {
        for (Thread t : T) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (NullPointerException ignored) {
            }
        }
    }

    public static String reverseComplete(String s) {
        return new StringBuilder(complete(s)).reverse().toString();
    }

    public static String complete(String s) {
        StringBuilder c_s = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            c_s.append(Constant.BASE_ALPHABET_COMPLETE.get(s.charAt(i)));
        }
        return c_s.toString();
    }

    public static boolean validDegenerateCodon(String codon) {
        for (char b : codon.toCharArray()) {
            if (!Constant.AMBIGUOUS_BASE_TABLE.containsKey(Character.toUpperCase(b))) {
                return false;
            }
        }
        return true;
    }

    /**
     * 
     * @param c1
     * @param c2
     * @return 返回汉明距离
     */
    public static int hammingDis(char[] c1, char[] c2) {
        if (c1.length != c2.length) {
            return -1;
        }
        int distance = 0;
        for (int i = 0; i < c1.length; i++) {
            if (c1[i] != c2[i]) {
                distance++;
            }
        }
        return distance;
    }

    /**
     * 获取子串在原字符串中的所有索引
     * 
     * @param input
     * @param substring
     * @param is_overlap 子串之间是否可以重叠
     * @return 如果没有匹配的子串返回空数组
     */
    public static int[] getSubstringIndices(String input, String substring, boolean is_overlap) {
        ArrayList<Integer> indicesList = new ArrayList<>();
        int index = input.indexOf(substring);

        while (index >= 0) {
            indicesList.add(index);
            if (is_overlap) {
                index = input.indexOf(substring, index + 1);
            } else {
                index = input.indexOf(substring, index + substring.length());
            }
        }

        // 将 ArrayList 转换为 int 数组
        int[] indicesArray = new int[indicesList.size()];
        for (int i = 0; i < indicesList.size(); i++) {
            indicesArray[i] = indicesList.get(i);
        }

        return indicesArray;
    }

    public static int getCigarCount(SAMRecord record) {
        return getCigarCount(record, CigarOperator.M);
    }

    public static int getCigarCount(SAMRecord record, CigarOperator operator) {
        if (operator == null) {
            return 0;
        }
        Cigar cigar = record.getCigar();
        int count = 0;
        for (CigarElement cigarElement : cigar.getCigarElements()) {
            if (cigarElement.getOperator() == operator) {
                count += cigarElement.getLength();
            }
        }
        return count;
    }

    public static double calculateGini(double[] values) {
        Arrays.sort(values);
        double sumOfAbsoluteDifferences = 0.0;
        double sumOfValues = 0.0;

        for (int i = 0; i < values.length; i++) {
            for (int j = 0; j < values.length; j++) {
                sumOfAbsoluteDifferences += Math.abs(values[i] - values[j]);
            }
            sumOfValues += values[i];
        }

        double meanOfValues = sumOfValues / values.length;
        return sumOfAbsoluteDifferences / (2 * values.length * values.length * meanOfValues);
    }

    /**
	 * 组内点的距离近小于阈值
	 * @param distanceMatrix
	 * @param max_dist
	 * @return 一组有效的索引值，或 null 如果行列长度不一致
	 */
	public static List<Integer> minMaxDistance(Matrix<String,String> distanceMatrix, double max_dist){
		if(distanceMatrix.getDimension()[0] != distanceMatrix.getDimension()[1]){
			return null;
		}
		int list_len = distanceMatrix.getDimension()[0];
		HashSet<Integer> index_set = new HashSet<>();
		for (int i = 0; i < list_len; i++) {
			index_set.add(i);
		}
		ArrayList<Integer> valid_index_list = new ArrayList<>();
		int min_distance_index = (int) (Math.random()*list_len);
		valid_index_list.add(min_distance_index);
		index_set.remove(min_distance_index);

		double[] current_sum_distance = distanceMatrix.getRowData(min_distance_index);
		double[] current_max_distance = distanceMatrix.getRowData(min_distance_index);

		double min_distance,min_sum;
		while (true) {
			min_distance = Double.POSITIVE_INFINITY;
			min_sum = Double.POSITIVE_INFINITY;
			min_distance_index = -1;

			Integer[] index_array = index_set.toArray(new Integer[0]);
			for (int index : index_array) {
				double max_distance_c = current_max_distance[index];
				if(max_distance_c > max_dist){
					index_set.remove(index);
					continue;
				}
				double sum_distance_c = current_sum_distance[index];
				if ((max_distance_c < min_distance) || (max_distance_c == min_distance && sum_distance_c < min_sum)) {
					min_distance = max_distance_c;
					min_sum = sum_distance_c;
					min_distance_index = index;
				}
			}
			if (min_distance_index < 0) {
				break;
			}
			else{
				valid_index_list.add(min_distance_index);
				index_set.remove(min_distance_index);
				for (int i : index_set) {
					double value = distanceMatrix.getValue(i, min_distance_index);
					current_max_distance[i] = Math.max(current_max_distance[i], value);
					current_sum_distance[i] += value;
				}
			}
		}


		return valid_index_list;
	}

	/**
	 * 组内点的距离近大于阈值
	 * 
	 * @param distanceMatrix
	 * @param max_dist
	 * @return 一组有效的索引值，或 null 如果行列长度不一致
	 */
	public static List<Integer> maxMinDistance(Matrix<String, String> distanceMatrix, double min_dist) {
		if (distanceMatrix.getDimension()[0] != distanceMatrix.getDimension()[1]) {
			return null;
		}
		int list_len = distanceMatrix.getDimension()[0];
		HashSet<Integer> index_set = new HashSet<>();
		for (int i = 0; i < list_len; i++) {
			index_set.add(i);
		}
		ArrayList<Integer> valid_index_list = new ArrayList<>();
		int max_distance_index = (int) (Math.random() * list_len);
		valid_index_list.add(max_distance_index);
		index_set.remove(max_distance_index);

		double[] current_sum_distance = distanceMatrix.getRowData(max_distance_index);
		double[] current_min_distance = distanceMatrix.getRowData(max_distance_index);

		double max_distance, max_sum;
		while (true) {
			max_distance = Double.NEGATIVE_INFINITY;
			max_sum = Double.NEGATIVE_INFINITY;
			max_distance_index = -1;

			Integer[] index_array = index_set.toArray(new Integer[0]);
			for (int index : index_array) {
				double max_distance_c = current_min_distance[index];
				if (max_distance_c < min_dist) {
					index_set.remove(index);
					continue;
				}
				double sum_distance_c = current_sum_distance[index];
				if ((max_distance_c > max_distance) || (max_distance_c == max_distance && sum_distance_c > max_sum)) {
					max_distance = max_distance_c;
					max_sum = sum_distance_c;
					max_distance_index = index;
				}
			}
			if (max_distance_index < 0) {
				break;
			} else {
				valid_index_list.add(max_distance_index);
				index_set.remove(max_distance_index);
				for (int i : index_set) {
					double value = distanceMatrix.getValue(i, max_distance_index);
					current_min_distance[i] = Math.min(current_min_distance[i], value);
					current_sum_distance[i] += value;
				}
			}
		}

		return valid_index_list;
	}

    public static HashMap<Character,Integer> SeqBaseCalculate(String s){

		String upper_str = s.toUpperCase();
        HashMap<Character,Integer> res_map = new HashMap<>();
        for (char base : upper_str.toCharArray()) {
            // 不存在时初始化为1，存在则累加
            res_map.merge(base, 1, Integer::sum);
        }
		return res_map;
	}

    public static List<FastqRecord> getFastqList(File input_file, int record_num) throws FileNotFoundException{
		List<FastqRecord> record_list = new ArrayList<>();
		FastqReader reader = new FastqReader(new BufferedReader(new FileReader(input_file)));
		int record_count = 0;
		for (FastqRecord fastqRecord : reader) {
			record_list.add(fastqRecord);
			record_count++;
			if (record_num > 0 && record_count >= record_num) {
				break;
			}
		}
		reader.close();
		return record_list;
	}

    public static List<List<Cell>> getContentFromExecl(File input_File) throws Exception{
		Workbook in_wb = new XSSFWorkbook(input_File);
		String sheet_name = in_wb.getSheetName(0);
		return getContentFromExecl(in_wb, sheet_name);
	}

	public static List<List<Cell>> getContentFromExecl(File input_File, String sheet_name) throws Exception{
		return getContentFromExecl(new XSSFWorkbook(input_File), sheet_name);
	}

	public static List<List<Cell>> getContentFromExecl(Workbook in_wb, String sheet_name) throws Exception {
		XlsxReader reader = new XlsxReader(in_wb);
		List<List<Cell>> content_list = reader.getConcent(sheet_name);
		reader.close();
		return content_list;
	}

    public static List<List<String>> getContentFromCSV(File input_file) throws Exception {
		return getContentFromText(input_file, ",");
    }

    public static List<List<String>> getContentFromText(File input_file, String delimiter) throws Exception {
		List<List<String>> content_list = new LinkedList<>();
		BufferedReader reader = new BufferedReader(new FileReader(input_file));
		String line;
		while ((line = reader.readLine()) != null) {
			line = line.replace("\n", "").replace("\r", "").strip();
			List<String> row_list = new ArrayList<>();
			for (String value : line.split(delimiter)) {
				row_list.add(value);
			}
			content_list.add(row_list);
		}
		reader.close();
		content_list = new ArrayList<>(content_list);
		return content_list;
	}

    public static Matrix<String,String> getMatrixFromDenseFile(File input_file) throws Exception{
		List<List<String>> content_list = getContentFromCSV(input_file);
		Set<String> row_set = new HashSet<>();
		Set<String> col_set = new HashSet<>();
		for (String col_name : content_list.get(0)) {
			if (col_name != null && !col_name.isEmpty()) {
				col_set.add(col_name);
			}
		}
		for (int i = 1; i < content_list.size(); i++) {
			row_set.add(content_list.get(i).get(0));
		}
		Matrix<String,String> matrix = new Matrix<>(row_set.toArray(new String[0]), col_set.toArray(new String[0]), 0);
		for (int i = 1; i < content_list.size(); i++) {
			String row_name = content_list.get(i).get(0);
			for (int j = 1; j < content_list.get(i).size(); j++) {
				String col_name = content_list.get(0).get(j);
				matrix.setValue(row_name, col_name, Double.valueOf(content_list.get(i).get(j)));
			}
		}
		return matrix;
	}

	public static Matrix<String,String> getMatrixFromSpareFile(File input_file, String delimiter) throws Exception{
        if (delimiter == null || delimiter.isEmpty()) {
            delimiter = ",";
        }
		List<List<String>> content_list = getContentFromText(input_file, delimiter);
		Set<String> row_set = new HashSet<>();
		Set<String> col_set = new HashSet<>();
		for (int i = 1; i < content_list.size(); i++) {
			row_set.add(content_list.get(i).get(0));
			col_set.add(content_list.get(i).get(1));
		}
		Matrix<String, String> matrix = new Matrix<>(row_set.toArray(new String[0]), col_set.toArray(new String[0]), 0);
		for (int i = 1; i < content_list.size(); i++) {
			matrix.setValue(content_list.get(i).get(0), content_list.get(i).get(1), Double.valueOf(content_list.get(i).get(2)));
		}
		return matrix;
	}

}

